(* ******************************************
  *                                          *
  * Graphic Scene Engine, http://glscene.org *
  *                                          *
  ******************************************* *)

unit Scene.Formats.FileMD3;

(* FileMD3 - File loading methods for the MD3 file format *)

interface

uses
  System.Classes,
  Scene.VectorTypes;

type
  // Quake3 MD3 structure types
  TMD3Tag = record
    strName: array [0 .. 63] of AnsiChar;
    vPosition: TVector3f;
    rotation: TMatrix3f;
  end;

  // This part of the MD3 structure calls 2 things:
  // A frame and a bone. It doesn't matter because we don't use it
  (* TMD3Frame = record
    min_bound,max_bounds,
    local_origin  : TVector3f;
    radius        : single;
    name          : array[0..15] of char;
    end; *)
  TMD3Bone = record
    mins, maxs, position: TVector3f;
    scale: single;
    creator: array [0 .. 15] of AnsiChar;
  end;

  TMD3Triangle = record
    vertex: TVector3s; // value/64 to get real number position
    normal: TVector2b; // Latitude, Longitude
  end;

  TMD3Face = record
    vertexIndices: TVector3i;
  end;

  TMD3TexCoord = record
    textureCoord: TVector2f;
  end;

  TMD3Skin = record
    strName: array [0 .. 63] of AnsiChar;
    shaderIndex: Integer;
  end;

  TMD3Header = record
    fileID: array [0 .. 3] of AnsiChar;
    version: Integer;
    strFile: array [0 .. 63] of AnsiChar;
    flags, numFrames, numTags, numMeshes, numMaxSkins, headerSize, tagStart,
      tagEnd, fileSize: Integer;
  end;

  TMD3MeshHeader = record
    meshID: array [0 .. 3] of AnsiChar;
    strName: array [0 .. 63] of AnsiChar;
    flags, numMeshFrames, numSkins, numVertices, numTriangles, triStart,
      headerSize, uvStart, vertexStart, meshSize: Integer;
  end;

  TMD3MeshData = record
    MeshHeader: TMD3MeshHeader;
    Skins: array of TMD3Skin;
    Triangles: array of TMD3Face;
    TexCoords: array of TMD3TexCoord;
    Vertices: array of TMD3Triangle;
  end;

  TFileMD3 = class
  public
    ModelHeader: TMD3Header;
    Bones: array of TMD3Bone;
    Tags: array of TMD3Tag;
    MeshData: array of TMD3MeshData;
    procedure LoadFromStream(aStream: TStream);
  end;

// ===================================================================
implementation
// ===================================================================

// ------------------
// ------------------ TFileMD3 ------------------
// ------------------

procedure TFileMD3.LoadFromStream(aStream: TStream);
var
  i: Integer;
  meshOffset: LongInt;
begin
  aStream.Read(ModelHeader, sizeof(ModelHeader));

  // Test for correct file ID and version
  Assert(ModelHeader.fileID = 'IDP3', 'Incorrect MD3 file ID');
  Assert(ModelHeader.version = 15, 'Incorrect MD3 version number');

  // Read in the bones
  SetLength(Bones, ModelHeader.numFrames);
  aStream.Read(Bones[0], sizeof(TMD3Bone) * ModelHeader.numFrames);

  // Read in the Tags
  SetLength(Tags, ModelHeader.numFrames * ModelHeader.numTags);
  if ModelHeader.numTags > 0 then
    aStream.Read(Tags[0], sizeof(TMD3Tag) * ModelHeader.numFrames *
      ModelHeader.numTags);

  // Read in the Mesh data
  meshOffset := aStream.position;
  SetLength(MeshData, ModelHeader.numMeshes);
  for i := 0 to ModelHeader.numMeshes - 1 do
  begin
    with MeshData[i] do
    begin
      aStream.position := meshOffset;
      aStream.Read(MeshHeader, sizeof(MeshHeader));
      // Set up the dynamic arrays
      SetLength(Skins, MeshHeader.numSkins);
      SetLength(Triangles, MeshHeader.numTriangles);
      SetLength(TexCoords, MeshHeader.numVertices);
      SetLength(Vertices, MeshHeader.numVertices * MeshHeader.numMeshFrames);
      // Skins
      aStream.Read(Skins[0], sizeof(TMD3Skin) * MeshHeader.numSkins);
      // Face data
      aStream.position := meshOffset + MeshHeader.triStart;
      aStream.Read(Triangles[0], sizeof(TMD3Face) * MeshHeader.numTriangles);
      // Texture coordinates
      aStream.position := meshOffset + MeshHeader.uvStart;
      aStream.Read(TexCoords[0], sizeof(TMD3TexCoord) * MeshHeader.numVertices);
      // Vertices
      aStream.position := meshOffset + MeshHeader.vertexStart;
      aStream.Read(Vertices[0], sizeof(TMD3Triangle) * MeshHeader.numMeshFrames
        * MeshHeader.numVertices);
      // Increase the offset
      meshOffset := meshOffset + MeshHeader.meshSize;
    end;
  end;
end;

end.
